package dkbta.ontology.loading;

import static dkbta.ontology.elements.ElementType.CONTEXT;
import static dkbta.ontology.elements.ElementType.PATTERN;
import static dkbta.ontology.elements.ElementType.REPEATINGPATTERN;
import static dkbta.ontology.elements.ElementType.STAT;
import static dkbta.ontology.elements.ElementType.SUBJECTSTAT;
import static dkbta.ontology.loading.OntologyParser.parseValueConsdition;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import dkbta.ontology.Ontology;
import dkbta.ontology.elements.ElementType;
import dkbta.ontology.elements.context.ContextDef;
import dkbta.ontology.elements.context.Induction;
import dkbta.ontology.loading.OntologyParser.ElementParser;
import dkbta.ontology.values.ValueCondition;
import dkbta.util.Pair;
import dkbta.util.time.ISODuration;

final class ContextParser implements ElementParser{
	private final Set<ElementType> _invalidInductionElements = EnumSet.of(CONTEXT,
		PATTERN, REPEATINGPATTERN, STAT, SUBJECTSTAT);
	
	private final Ontology _ontology;

	public ContextParser(Ontology ontology){
		_ontology = ontology;
	}

	@Override
	public ElementType getElementType(){
		return ElementType.CONTEXT;
	}

	public void parseElement(Element context, String name, String componentType)
			throws OntologyException{
		Set<String> subContexts = parseSubContexts(context, name);
		Collection<Induction> inductions = parseInductions(context, name);
		ContextDef cd = new ContextDef(name, componentType, subContexts, inductions);
		_ontology.addContext(cd);
	}

	private Collection<Induction> parseInductions(Element context, String name)
			throws OntologyException{
		Element inductionsElement = OntologyParser.getXmlElement(context, "Inductions");
		if (inductionsElement == null){
			throw new OntologyException("No inductions element");
		}
		// Parsing each of the inductions
		NodeList inductionNodes = inductionsElement.getElementsByTagName("Induction");
		int length = inductionNodes.getLength();
		Collection<Induction> inductions = new ArrayList<Induction>(length);
		for (int i = 0; i < length; ++i){
			try{
				parseInduction((Element)inductionNodes.item(i), inductions);
			}catch(Exception e){
				throw new OntologyException("Unable to parse induction #" + i
						+ " of the context " + name, e);
			}
		}
		if (inductions.isEmpty()){
			throw new OntologyException("Context must be have at least one induction");
		}

		return inductions;
	}

	private void parseInduction(Element induction, Collection<Induction> inductions)
			throws OntologyException{
		Pair<Boolean, ISODuration> startInfo = null;
		Pair<Boolean, ISODuration> endInfo = null;
		ValueCondition valConstraint = null;

		NodeList childNodes = induction.getChildNodes();
		int length = childNodes.getLength();
		for (int i = 0; i < length; ++i){
			Node node = childNodes.item(i);
			if (node.getNodeType() != Node.ELEMENT_NODE){
				continue;
			}
			Element element = (Element)node;
			String tag = element.getTagName();
			if ("Starts".equalsIgnoreCase(tag)){
				startInfo = parseGapAndRelativeTo(element);
			}else if ("Ends".equalsIgnoreCase(tag)){
				endInfo = parseGapAndRelativeTo(element);
			}else{
				valConstraint = parseValueConsdition(element, tag,
					_invalidInductionElements, true);
			}
		}
		if (startInfo == null || endInfo == null || valConstraint == null){
			throw new OntologyException(
					"Missing either start info, end info or value constraint");
		}
		inductions.add(new Induction(valConstraint, startInfo.getFirst(), startInfo
				.getSecond(), endInfo.getFirst(), endInfo.getSecond()));
	}

	private Pair<Boolean, ISODuration> parseGapAndRelativeTo(Element element)
			throws OntologyException{
		Boolean relativeToStart = null;
		ISODuration gap = null;
		String gapStr = element.getAttribute("gap");
		try{
			gap = new ISODuration(gapStr);
		}catch(Exception e){
			throw new OntologyException("Invalid gap attribute: " + gapStr, e);
		}

		String relativeTo = element.getAttribute("relativeTo");
		if ("End".equalsIgnoreCase(relativeTo)){
			relativeToStart = false;
		}else if ("Start".equalsIgnoreCase(relativeTo)){
			relativeToStart = true;
		}else{
			throw new OntologyException("Invalid relativeTo attribute: " + relativeTo);
		}
		return new Pair<Boolean, ISODuration>(relativeToStart, gap);
	}

	private Set<String> parseSubContexts(Element context, String contextName)
			throws OntologyException{
		Element xml = OntologyParser.getXmlElement(context, "SubContexts");
		if (xml == null){
			return null;
		}

		NodeList subContextNodes = xml.getElementsByTagName("SubContext");
		Set<String> subContexts = new HashSet<String>(subContextNodes.getLength());
		for (int i = 0; i < subContextNodes.getLength(); ++i){
			Element subContextElement = (Element)subContextNodes.item(i);
			String subContext = subContextElement.getAttribute("name");
			if (subContext.isEmpty()){
				throw new OntologyException("SubContext without a name");
			}
			if (subContexts.contains(subContext)){
				throw new OntologyException("The sub-context " + subContext
						+ " appears at least twice");
			}
			subContexts.add(subContext);
		}
		return subContexts;
	}
}